#include "tlib.h"
#include <stdio.h>
#include <iostream>

DWORD TWinVersion = ::GetVersion();

void TSwitchToThisWindow(HWND hWnd, BOOL flg) {
    static BOOL once = FALSE;
    static void(WINAPI * pSwitchToThisWindow)(HWND, BOOL);

    if (!once) {
        pSwitchToThisWindow = (void(WINAPI *)(HWND, BOOL))GetProcAddress(
            ::GetModuleHandle("user32"), "SwitchToThisWindow");
        once = TRUE;
    }

    if (pSwitchToThisWindow) {
        pSwitchToThisWindow(hWnd, flg);
    }
}

/*----------------------------------------------------------------
 * TWin
 *---------------------------------------------------------------*/

TWin::TWin(TWin *_parent) {
    hWnd = 0;
    hAccel = NULL;
    rect.left = CW_USEDEFAULT;
    rect.right = CW_USEDEFAULT;
    rect.top = CW_USEDEFAULT;
    rect.bottom = CW_USEDEFAULT;
    orgRect = rect;
    parent = _parent;
    isUnicode = TRUE;
    scrollHack = TRUE;
    modalCount = 0;
    twinId = TApp::GenTWinID();
}

TWin::~TWin() { Destroy(); }

BOOL TWin::Create(HINSTANCE hI) {
    TApp::GetApp()->AddWin(this);

    return true;
}

void TWin::Destroy(void) {
    if (::IsWindow(hWnd)) {
        ::DestroyWindow(hWnd);
        hWnd = 0;
    }
}

void TWin::Show() {
    ::ShowWindow(hWnd, SW_SHOW);
    ::UpdateWindow(hWnd);
}

void TWin::Hide() {
    ::ShowWindow(hWnd, SW_HIDE);
    ::UpdateWindow(hWnd);
}

LRESULT TWin::WinProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    BOOL done = FALSE;
    LRESULT result = 0;

    switch (uMsg) {
    case WM_CREATE:
        GetWindowRect(&orgRect);
        done = EvCreate(lParam);
        break;

    case WM_CLOSE:
        done = EvClose();
        break;

    case WM_COMMAND:
        done = EvCommand(HIWORD(wParam), LOWORD(wParam), lParam);
        break;

    case WM_SYSCOMMAND:
        done = EvSysCommand(wParam, MAKEPOINTS(lParam));
        break;

    case WM_TIMER:
        done = EvTimer(wParam, (TIMERPROC)lParam);
        break;

    case WM_DESTROY:
        done = EvDestroy();
        break;

    case WM_NCDESTROY:
        if (!::IsIconic(hWnd)) {
            GetWindowRect(&rect);
        }
        if (!EvNcDestroy()) {
            DefWindowProc(uMsg, wParam, lParam);
        }
        done = TRUE;
        TApp::GetApp()->DelWin(this);
        hWnd = 0;
        break;

    case WM_QUERYENDSESSION:
        result = EvQueryEndSession((BOOL)wParam, (BOOL)lParam);
        done = TRUE;
        break;

    case WM_ENDSESSION:
        done = EvEndSession((BOOL)wParam, (BOOL)lParam);
        break;

    case WM_QUERYOPEN:
        result = EvQueryOpen();
        done = TRUE;
        break;

    case WM_PAINT:
        done = EvPaint();
        break;

    case WM_NCPAINT:
        done = EvNcPaint((HRGN)wParam);
        break;

    case WM_SIZE:
        done = EvSize((UINT)wParam, LOWORD(lParam), HIWORD(lParam));
        break;

    case WM_MOVE:
        done = EvMove(LOWORD(lParam), HIWORD(lParam));
        break;

    case WM_SHOWWINDOW:
        done = EvShowWindow((BOOL)wParam, (int)lParam);
        break;

    case WM_GETMINMAXINFO:
        done = EvGetMinMaxInfo((MINMAXINFO *)lParam);
        break;

    case WM_SETCURSOR:
        result = done =
            EvSetCursor((HWND)wParam, LOWORD(lParam), HIWORD(lParam));
        break;

    case WM_MOUSEMOVE:
        done = EvMouseMove((UINT)wParam, MAKEPOINTS(lParam));
        break;

    case WM_NCHITTEST:
        done = EvNcHitTest(MAKEPOINTS(lParam), &result);
        break;

    case WM_MEASUREITEM:
        result = done =
            EvMeasureItem((UINT)wParam, (LPMEASUREITEMSTRUCT)lParam);
        break;

    case WM_DRAWITEM:
        result = done = EvDrawItem((UINT)wParam, (LPDRAWITEMSTRUCT)lParam);
        break;

    case WM_NOTIFY:
        result = done = EvNotify((UINT)wParam, (LPNMHDR)lParam);
        break;

    case WM_CONTEXTMENU:
        result = done = EvContextMenu((HWND)wParam, MAKEPOINTS(lParam));
        break;

    case WM_HOTKEY:
        result = done = EvHotKey((int)wParam);
        break;

    case WM_ACTIVATEAPP:
        done = EvActivateApp((BOOL)wParam, (DWORD)lParam);
        break;

    case WM_ACTIVATE:
        EvActivate(LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
        break;

    case WM_DROPFILES:
        done = EvDropFiles((HDROP)wParam);
        break;

    case WM_CHAR:
        done = EvChar((WCHAR)wParam, lParam);
        break;

    case WM_WINDOWPOSCHANGING:
        done = EvWindowPosChanging((WINDOWPOS *)lParam);
        break;

    case WM_WINDOWPOSCHANGED:
        done = EvWindowPosChanged((WINDOWPOS *)lParam);
        break;

    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_NCLBUTTONUP:
    case WM_NCRBUTTONUP:
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_NCLBUTTONDOWN:
    case WM_NCRBUTTONDOWN:
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDBLCLK:
    case WM_NCLBUTTONDBLCLK:
    case WM_NCRBUTTONDBLCLK:
        done = EventButton(uMsg, (int)wParam, MAKEPOINTS(lParam));
        break;

    case WM_KEYUP:
    case WM_KEYDOWN:
        done = EventKey(uMsg, (int)wParam, (LONG)lParam);
        break;

    case WM_HSCROLL:
    case WM_VSCROLL:
        done = EventScrollWrapper(uMsg, LOWORD(wParam), HIWORD(wParam),
                                  (HWND)lParam);
        break;

    case WM_ENTERMENULOOP:
    case WM_EXITMENULOOP:
        done = EventMenuLoop(uMsg, (BOOL)wParam);
        break;

    case WM_INITMENU:
    case WM_INITMENUPOPUP:
        done = EventInitMenu(uMsg, (HMENU)wParam, LOWORD(lParam),
                             (BOOL)HIWORD(lParam));
        break;

    case WM_MENUSELECT:
        done = EvMenuSelect(LOWORD(wParam), HIWORD(wParam), (HMENU)lParam);
        break;

    case WM_CTLCOLORBTN:
    case WM_CTLCOLORDLG:
    case WM_CTLCOLOREDIT:
    case WM_CTLCOLORLISTBOX:
    case WM_CTLCOLORMSGBOX:
    case WM_CTLCOLORSCROLLBAR:
    case WM_CTLCOLORSTATIC:
        done =
            EventCtlColor(uMsg, (HDC)wParam, (HWND)lParam, (HBRUSH *)&result);
        break;

    case WM_KILLFOCUS:
    case WM_SETFOCUS:
        done = EventFocus(uMsg, (HWND)wParam);
        break;

    default:
        if (uMsg >= WM_APP && uMsg <= 0xBFFF) {
            result = done = EventApp(uMsg, wParam, lParam);
        } else if ((uMsg >= WM_USER && uMsg < WM_APP) ||
                   (uMsg >= 0xC000 && uMsg <= 0xFFFF)) {
            result = done = EventUser(uMsg, wParam, lParam);
        } else {
            result = done = EventSystem(uMsg, wParam, lParam);
        }
        break;
    }

    return done ? result : DefWindowProc(uMsg, wParam, lParam);
}

LRESULT TWin::DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    return isUnicode ? ::DefWindowProcW(hWnd, uMsg, wParam, lParam)
                     : ::DefWindowProcA(hWnd, uMsg, wParam, lParam);
}

BOOL TWin::PreProcMsg(MSG *msg) {
    if (hAccel)
        return ::TranslateAccelerator(hWnd, hAccel, msg);

    return FALSE;
}

BOOL TWin::EvCommand(WORD wNotifyCode, WORD wID, LPARAM hwndCtl) {
    return FALSE;
}

BOOL TWin::EvSysCommand(WPARAM uCmdType, POINTS pos) { return FALSE; }

BOOL TWin::EvCreate(LPARAM lParam) { return FALSE; }

BOOL TWin::EvClose(void) { return FALSE; }

BOOL TWin::EvDestroy(void) { return FALSE; }

BOOL TWin::EvNcDestroy(void) { return FALSE; }

BOOL TWin::EvQueryEndSession(BOOL nSession, BOOL nLogOut) { return TRUE; }

BOOL TWin::EvEndSession(BOOL nSession, BOOL nLogOut) { return TRUE; }

BOOL TWin::EvQueryOpen(void) { return TRUE; }

BOOL TWin::EvPaint(void) { return FALSE; }

BOOL TWin::EvNcPaint(HRGN hRgn) { return FALSE; }

BOOL TWin::EvSize(UINT fwSizeType, WORD nWidth, WORD nHeight) { return FALSE; }

BOOL TWin::EvMove(int xpos, int ypos) { return FALSE; }

BOOL TWin::EvShowWindow(BOOL fShow, int fnStatus) { return FALSE; }

BOOL TWin::EvGetMinMaxInfo(MINMAXINFO *info) { return FALSE; }

BOOL TWin::EvTimer(WPARAM timerID, TIMERPROC proc) { return FALSE; }

BOOL TWin::EvSetCursor(HWND cursorWnd, WORD nHitTest, WORD wMouseMsg) {
    return FALSE;
}

BOOL TWin::EvMouseMove(UINT fwKeys, POINTS pos) { return FALSE; }

BOOL TWin::EvNcHitTest(POINTS pos, LRESULT *result) { return FALSE; }

BOOL TWin::EvMeasureItem(UINT ctlID, MEASUREITEMSTRUCT *lpMis) { return FALSE; }

BOOL TWin::EvDrawItem(UINT ctlID, DRAWITEMSTRUCT *lpDis) { return FALSE; }

BOOL TWin::EvMenuSelect(UINT uItem, UINT fuFlag, HMENU hMenu) { return FALSE; }

BOOL TWin::EvDropFiles(HDROP hDrop) { return FALSE; }

BOOL TWin::EvNotify(UINT ctlID, NMHDR *pNmHdr) { return FALSE; }

BOOL TWin::EvContextMenu(HWND childWnd, POINTS pos) { return FALSE; }

BOOL TWin::EvHotKey(int hotKey) { return FALSE; }

BOOL TWin::EvActivateApp(BOOL fActivate, DWORD dwThreadID) { return FALSE; }

BOOL TWin::EvActivate(BOOL fActivate, DWORD fMinimized, HWND hActiveWnd) {
    return FALSE;
}

BOOL TWin::EvChar(WCHAR code, LPARAM keyData) { return FALSE; }

BOOL TWin::EvWindowPosChanged(WINDOWPOS *pos) { return FALSE; }

BOOL TWin::EvWindowPosChanging(WINDOWPOS *pos) { return FALSE; }

BOOL TWin::EventButton(UINT uMsg, int nHitTest, POINTS pos) { return FALSE; }

BOOL TWin::EventKey(UINT uMsg, int nVirtKey, LONG lKeyData) { return FALSE; }

BOOL TWin::EventMenuLoop(UINT uMsg, BOOL fIsTrackPopupMenu) { return FALSE; }

BOOL TWin::EventInitMenu(UINT uMsg, HMENU hMenu, UINT uPos, BOOL fSystemMenu) {
    return FALSE;
}

BOOL TWin::EventCtlColor(UINT uMsg, HDC hDcCtl, HWND hWndCtl, HBRUSH *result) {
    return FALSE;
}

BOOL TWin::EventFocus(UINT uMsg, HWND hFocusWnd) { return FALSE; }

BOOL TWin::EventScrollWrapper(UINT uMsg, int nCode, int nPos, HWND scrollBar) {
    if (scrollHack) {
        if (nCode == SB_THUMBTRACK || nCode == SB_THUMBPOSITION) {
            SCROLLINFO si = {sizeof(si), SIF_TRACKPOS};
            if (::GetScrollInfo(hWnd, uMsg == WM_HSCROLL ? SB_HORZ : SB_VERT,
                                &si)) {
                nPos = si.nTrackPos;
            }
        }
    }
    return EventScroll(uMsg, nCode, nPos, scrollBar);
}

BOOL TWin::EventScroll(UINT uMsg, int nCode, int nPos, HWND scrollBar) {
    scrollHack = FALSE;
    return FALSE;
}

BOOL TWin::EventApp(UINT uMsg, WPARAM wParam, LPARAM lParam) { return FALSE; }

BOOL TWin::EventUser(UINT uMsg, WPARAM wParam, LPARAM lParam) { return FALSE; }

BOOL TWin::EventSystem(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    return FALSE;
}

UINT TWin::GetDlgItemText(int ctlId, LPSTR buf, int len) {
    return ::GetDlgItemText(hWnd, ctlId, buf, len);
}

UINT TWin::GetDlgItemTextW(int ctlId, WCHAR *buf, int len) {
    return ::GetDlgItemTextW(hWnd, ctlId, buf, len);
}

BOOL TWin::SetDlgItemText(int ctlId, LPCSTR buf) {
    return ::SetDlgItemText(hWnd, ctlId, buf);
}

BOOL TWin::SetDlgItemTextW(int ctlId, const WCHAR *buf) {
    return ::SetDlgItemTextW(hWnd, ctlId, buf);
}

int TWin::GetDlgItemInt(int ctlId, BOOL *err, BOOL sign) {
    return (int)::GetDlgItemInt(hWnd, ctlId, err, sign);
}

BOOL TWin::SetDlgItemInt(int ctlId, int val, BOOL sign) {
    return ::SetDlgItemInt(hWnd, ctlId, val, sign);
}

HWND TWin::GetDlgItem(int ctlId) { return ::GetDlgItem(hWnd, ctlId); }

BOOL TWin::CheckDlgButton(int ctlId, UINT check) {
    return ::CheckDlgButton(hWnd, ctlId, check);
}

UINT TWin::IsDlgButtonChecked(int ctlId) {
    return ::IsDlgButtonChecked(hWnd, ctlId);
}

BOOL TWin::IsWindowVisible(void) { return ::IsWindowVisible(hWnd); }

BOOL TWin::EnableWindow(BOOL is_enable) {
    return ::EnableWindow(hWnd, is_enable);
}

int TWin::MessageBox(LPCSTR msg, LPCSTR title, UINT style) {
    modalCount++;
    int ret = ::MessageBox(hWnd, msg, title, style);
    modalCount--;

    return ret;
}

int TWin::MessageBoxW(LPCWSTR msg, LPCWSTR title, UINT style) {
    modalCount++;
    int ret = ::MessageBoxW(hWnd, msg, title, style);
    modalCount--;

    return ret;
}

BOOL TWin::BringWindowToTop(void) { return ::BringWindowToTop(hWnd); }

BOOL TWin::SetForegroundWindow(void) { return ::SetForegroundWindow(hWnd); }

BOOL TWin::SetForceForegroundWindow(void) {
    DWORD foreId, targId, svTmOut;

    if (IsWinVista()) {
        TSwitchToThisWindow(hWnd, TRUE);
    }

    foreId = ::GetWindowThreadProcessId(::GetForegroundWindow(), NULL);
    targId = ::GetWindowThreadProcessId(hWnd, NULL);
    if (foreId != targId)
        ::AttachThreadInput(targId, foreId, TRUE);
    ::SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, (void *)&svTmOut,
                           0);
    ::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, 0, 0);
    BOOL ret = ::SetForegroundWindow(hWnd);
    ::SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0,
                           (void *)(DWORD_PTR)svTmOut, 0);
    if (foreId != targId)
        ::AttachThreadInput(targId, foreId, FALSE);

    return ret;
}

BOOL TWin::ShowWindow(int mode) { return ::ShowWindow(hWnd, mode); }

BOOL TWin::PostMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    return ::PostMessage(hWnd, uMsg, wParam, lParam);
}

BOOL TWin::PostMessageW(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    return ::PostMessageW(hWnd, uMsg, wParam, lParam);
}

LRESULT TWin::SendMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    return ::SendMessage(hWnd, uMsg, wParam, lParam);
}

LRESULT TWin::SendMessageW(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    return ::SendMessageW(hWnd, uMsg, wParam, lParam);
}

LRESULT TWin::SendDlgItemMessage(int idCtl, UINT uMsg, WPARAM wParam,
                                 LPARAM lParam) {
    return ::SendDlgItemMessage(hWnd, idCtl, uMsg, wParam, lParam);
}

LRESULT TWin::SendDlgItemMessageW(int idCtl, UINT uMsg, WPARAM wParam,
                                  LPARAM lParam) {
    return ::SendDlgItemMessageW(hWnd, idCtl, uMsg, wParam, lParam);
}

BOOL TWin::GetWindowRect(RECT *_rect) {
    return ::GetWindowRect(hWnd, _rect ? _rect : &rect);
}

BOOL TWin::GetClientRect(RECT *rc) { return ::GetClientRect(hWnd, rc); }

BOOL TWin::SetWindowPos(HWND hInsAfter, int x, int y, int cx, int cy,
                        UINT fuFlags) {
    return ::SetWindowPos(hWnd, hInsAfter, x, y, cx, cy, fuFlags);
}

HWND TWin::SetActiveWindow(void) { return ::SetActiveWindow(hWnd); }

int TWin::GetWindowText(LPSTR text, int size) {
    return ::GetWindowText(hWnd, text, size);
}

int TWin::GetWindowTextW(WCHAR *text, int size) {
    return ::GetWindowTextW(hWnd, text, size);
}

BOOL TWin::SetWindowText(LPCSTR text) { return ::SetWindowText(hWnd, text); }

BOOL TWin::SetWindowTextW(const WCHAR *text) {
    return ::SetWindowTextW(hWnd, text);
}

int TWin::GetWindowTextLengthW(void) { return ::GetWindowTextLengthW(hWnd); }

LONG_PTR TWin::GetWindowLong(int index) { return ::GetWindowLong(hWnd, index); }

LONG_PTR TWin::SetWindowLong(int index, LONG_PTR val) {
    return ::SetWindowLong(hWnd, index, val);
}

WORD TWin::GetWindowWord(int index) { return ::GetWindowWord(hWnd, index); }

WORD TWin::SetWindowWord(int index, WORD val) {
    return ::SetWindowWord(hWnd, index, val);
}

BOOL TWin::InvalidateRect(const RECT *rc, BOOL fErase) {
    return ::InvalidateRect(hWnd, rc, fErase);
}

BOOL TWin::MoveWindow(int x, int y, int cx, int cy, int bRepaint) {
    return ::MoveWindow(hWnd, x, y, cx, cy, bRepaint);
}

BOOL TWin::FitMoveWindow(int x, int y) {
    RECT rc;
    GetWindowRect(&rc);
    int cx = rc.right - rc.left;
    int cy = rc.bottom - rc.top;

    int start_x = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
    int start_y = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
    int end_x = start_x + ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
    int end_y = start_y + ::GetSystemMetrics(SM_CYVIRTUALSCREEN);

    if (x + cx > end_x)
        x = end_x - cx;
    if (x < start_x)
        x = start_x;
    if (y + cy > end_y)
        y = end_y - cy;
    if (y < start_y)
        y = start_y;

    return SetWindowPos(NULL, x, y, 0, 0,
                        SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER);
}

BOOL TWin::Sleep(UINT mSec) {
    if (mSec == 0 || sleepBusy)
        return TRUE;

    if (!::SetTimer(hWnd, TLIB_SLEEPTIMER, mSec, 0))
        return FALSE;
    sleepBusy = TRUE;

    MSG msg;
    while (::GetMessage(&msg, 0, 0, 0)) {
        if (msg.hwnd == hWnd && msg.wParam == TLIB_SLEEPTIMER) {
            ::KillTimer(hWnd, TLIB_SLEEPTIMER);
            break;
        }
        if (TApp::GetApp()->PreProcMsg(&msg))
            continue;

        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
    sleepBusy = FALSE;

    return TRUE;
}

BOOL TWin::Idle(void) {
    MSG msg;

    if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        if (TApp::GetApp()->PreProcMsg(&msg))
            return TRUE;

        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
        return TRUE;
    }

    return FALSE;
}
/*----------------------------------------------------------------
 * TDlg
 *---------------------------------------------------------------*/

TDlg::TDlg(UINT _resId, TWin *_parent) : TWin(_parent) {
    resId = _resId;
    modalFlg = FALSE;
    maxItems = 0;
    dlgItems = NULL;
}

TDlg::~TDlg() {
    if (hWnd) {
        EndDialog(IDCANCEL);
    }
    delete[] dlgItems;
}

BOOL TDlg::Create(HINSTANCE hInstance) {
    TApp::GetApp()->AddWin(this);

    hWnd = ::CreateDialogW(
        hInstance ? hInstance : TApp::GetInstance(), (WCHAR *)(DWORD_PTR)resId,
        parent ? parent->hWnd : NULL, (DLGPROC)TApp::WinProc);

    if (hWnd)
        return TRUE;
    else
        return TApp::GetApp()->DelWin(this), FALSE;
}

int TDlg::Exec(void) {
    TApp::GetApp()->AddWin(this);
    modalFlg = TRUE;
    if (parent)
        parent->modalCount++;

    int result =
        (int)::DialogBoxW(TApp::GetInstance(), (WCHAR *)(DWORD_PTR)resId,
                          parent ? parent->hWnd : NULL, (DLGPROC)TApp::WinProc);

    if (parent)
        parent->modalCount--;
    modalFlg = FALSE;
    return result;
}

void TDlg::Destroy(void) { EndDialog(IDCANCEL); }

LRESULT TDlg::WinProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    LRESULT result = 0;

    switch (uMsg) {
    case WM_INITDIALOG:
        if (rect.left != CW_USEDEFAULT &&
            !(::GetWindowLong(hWnd, GWL_STYLE) & WS_CHILD)) {
            MoveWindow(rect.left, rect.top, rect.right - rect.left,
                       rect.bottom - rect.top, FALSE);
        }
        if (rect.left == CW_USEDEFAULT) {
            GetWindowRect(&orgRect);
        }
        return EvCreate(lParam);

    case WM_CLOSE:
        EvClose();
        return 0;

    case WM_COMMAND:
        EvCommand(HIWORD(wParam), LOWORD(wParam), lParam);
        return 0;

    case WM_SYSCOMMAND:
        EvSysCommand(wParam, MAKEPOINTS(lParam));
        return 0;

    case WM_TIMER:
        EvTimer(wParam, (TIMERPROC)lParam);
        return 0;

    case WM_DESTROY:
        EvDestroy();
        return 0;

    case WM_NCDESTROY:
        if (!::IsIconic(hWnd))
            GetWindowRect(&rect);
        EvNcDestroy();
        TApp::GetApp()->DelWin(this);
        hWnd = 0;
        return 0;

    case WM_QUERYENDSESSION:
        result = EvQueryEndSession((BOOL)wParam, (BOOL)lParam);
        SetWindowLong(DWLP_MSGRESULT, result);
        return 0;

    case WM_ENDSESSION:
        EvEndSession((BOOL)wParam, (BOOL)lParam);
        return 0;

    case WM_QUERYOPEN:
        result = EvQueryOpen();
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_PAINT:
        EvPaint();
        return 0;

    case WM_NCPAINT:
        EvNcPaint((HRGN)wParam);
        return 0;

    case WM_SIZE:
        EvSize((UINT)wParam, LOWORD(lParam), HIWORD(lParam));
        return 0;

    case WM_MOVE:
        EvMove(LOWORD(lParam), HIWORD(lParam));
        return 0;

    case WM_SHOWWINDOW:
        EvShowWindow((BOOL)wParam, (int)lParam);
        return 0;

    case WM_GETMINMAXINFO:
        EvGetMinMaxInfo((MINMAXINFO *)lParam);
        return 0;

    case WM_SETCURSOR:
        result = EvSetCursor((HWND)wParam, LOWORD(lParam), HIWORD(lParam));
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_MOUSEMOVE:
        return EvMouseMove((UINT)wParam, MAKEPOINTS(lParam));

    case WM_NCHITTEST:
        EvNcHitTest(MAKEPOINTS(lParam), &result);
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_MEASUREITEM:
        result = EvMeasureItem((UINT)wParam, (LPMEASUREITEMSTRUCT)lParam);
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_DRAWITEM:
        result = EvDrawItem((UINT)wParam, (LPDRAWITEMSTRUCT)lParam);
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_NOTIFY:
        result = EvNotify((UINT)wParam, (LPNMHDR)lParam);
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_CONTEXTMENU:
        result = EvContextMenu((HWND)wParam, MAKEPOINTS(lParam));
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_HOTKEY:
        result = EvHotKey((int)wParam);
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_ACTIVATEAPP:
        EvActivateApp((BOOL)wParam, (DWORD)lParam);
        break;

    case WM_ACTIVATE:
        EvActivate(LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
        break;

    case WM_DROPFILES:
        EvDropFiles((HDROP)wParam);
        return 0;

    case WM_CHAR:
        EvChar((WCHAR)wParam, lParam);
        SetWindowLong(DWLP_MSGRESULT, 0);
        return 0;

    case WM_WINDOWPOSCHANGING:
        EvWindowPosChanging((WINDOWPOS *)lParam);
        SetWindowLong(DWLP_MSGRESULT, 0);
        return 0;

    case WM_WINDOWPOSCHANGED:
        EvWindowPosChanged((WINDOWPOS *)lParam);
        SetWindowLong(DWLP_MSGRESULT, 0);
        return 0;

    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    case WM_NCLBUTTONUP:
    case WM_NCRBUTTONUP:
    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    case WM_NCLBUTTONDOWN:
    case WM_NCRBUTTONDOWN:
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDBLCLK:
    case WM_NCLBUTTONDBLCLK:
    case WM_NCRBUTTONDBLCLK:
        EventButton(uMsg, (int)wParam, MAKEPOINTS(lParam));
        return 0;

    case WM_KEYUP:
    case WM_KEYDOWN:
        EventKey(uMsg, (int)wParam, (LONG)lParam);
        return 0;

    case WM_HSCROLL:
    case WM_VSCROLL:
        EventScrollWrapper(uMsg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
        return 0;

    case WM_ENTERMENULOOP:
    case WM_EXITMENULOOP:
        EventMenuLoop(uMsg, (BOOL)wParam);
        break;

    case WM_INITMENU:
    case WM_INITMENUPOPUP:
        EventInitMenu(uMsg, (HMENU)wParam, LOWORD(lParam),
                      (BOOL)HIWORD(lParam));
        return 0;

    case WM_MENUSELECT:
        EvMenuSelect(LOWORD(wParam), (BOOL)HIWORD(wParam), (HMENU)lParam);
        return 0;

    case WM_CTLCOLORBTN:
    case WM_CTLCOLORDLG:
    case WM_CTLCOLOREDIT:
    case WM_CTLCOLORLISTBOX:
    case WM_CTLCOLORMSGBOX:
    case WM_CTLCOLORSCROLLBAR:
    case WM_CTLCOLORSTATIC:
        EventCtlColor(uMsg, (HDC)wParam, (HWND)lParam, (HBRUSH *)&result);
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;

    case WM_KILLFOCUS:
    case WM_SETFOCUS:
        EventFocus(uMsg, (HWND)wParam);
        return 0;

    default:
        if ((uMsg >= WM_APP) && (uMsg <= 0xBFFF)) {
            result = EventApp(uMsg, wParam, lParam);
        } else if ((uMsg >= WM_USER && uMsg < WM_APP) ||
                   (uMsg >= 0xC000 && uMsg <= 0xFFFF)) {
            result = EventUser(uMsg, wParam, lParam);
        } else {
            result = EventSystem(uMsg, wParam, lParam);
        }
        SetWindowLong(DWLP_MSGRESULT, result);
        return result;
    }

    return FALSE;
}

BOOL TDlg::PreProcMsg(MSG *msg) {
    if (hAccel && ::TranslateAccelerator(hWnd, hAccel, msg))
        return TRUE;

    if (!modalFlg)
        return ::IsDialogMessage(hWnd, msg);

    return FALSE;
}

BOOL TDlg::EvSysCommand(WPARAM uCmdType, POINTS pos) { return FALSE; }

BOOL TDlg::EvCommand(WORD wNotifyCode, WORD wID, LPARAM hwndCtl) {
    switch (wID) {
    case IDOK:
    case IDCANCEL:
    case IDYES:
    case IDNO:
    case IDABORT:
    case IDIGNORE:
    case IDRETRY:
        EndDialog(wID);
        return TRUE;
    }

    return FALSE;
}

BOOL TDlg::EvQueryOpen(void) { return FALSE; }

BOOL TDlg::EvCreate(LPARAM lParam) { return TRUE; }

void TDlg::EndDialog(int result) {
    if (::IsWindow(hWnd)) {
        if (modalFlg)
            ::EndDialog(hWnd, result);
        else
            ::DestroyWindow(hWnd);
    }
}

int TDlg::SetDlgItem(UINT ctl_id, DWORD flags) {
    WINDOWPLACEMENT wp;
    wp.length = sizeof(wp);

    for (int i = 0; i < maxItems; i++) {
        DlgItem *item = dlgItems + i;
        if (item->id == ctl_id) {
            item->hWnd = GetDlgItem(ctl_id);
            item->flags = flags;
            return i;
        }
    }

#define BIG_ALLOC 50
    if ((maxItems % BIG_ALLOC) == 0) {
        DlgItem *p = (DlgItem *)realloc(dlgItems, (maxItems + BIG_ALLOC) *
                                                      sizeof(DlgItem));
        if (!p)
            return -1;
        dlgItems = p;
    }
    DlgItem *item = dlgItems + maxItems;

    item->hWnd = GetDlgItem(ctl_id);
    ::GetWindowPlacement(item->hWnd, &wp);
    item->wpos.x = wp.rcNormalPosition.left;
    item->wpos.y = wp.rcNormalPosition.top;
    item->wpos.cx = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
    item->wpos.cy = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;
    item->flags = flags;
    item->id = ctl_id;

    return maxItems++;
}

BOOL TDlg::FitDlgItems() {
    GetWindowRect(&rect);
    int xdiff = (rect.right - rect.left) - (orgRect.right - orgRect.left);
    int ydiff = (rect.bottom - rect.top) - (orgRect.bottom - orgRect.top);

    HDWP hdwp = ::BeginDeferWindowPos(maxItems); // MAX item number
    UINT dwFlg = SWP_SHOWWINDOW | SWP_NOZORDER;

    for (int i = 0; i < maxItems; i++) {
        DlgItem *item = dlgItems + i;
        DWORD f = item->flags;

        if (f & FIT_SKIP)
            continue;
        int x = (f & LEFT_FIT) == LEFT_FIT
                    ? item->wpos.x
                    : (f & HMID_FIT) == HMID_FIT ? item->wpos.x + xdiff / 2
                                                 : item->wpos.x + xdiff;
        int y = (f & TOP_FIT) == TOP_FIT
                    ? item->wpos.y
                    : (f & VMID_FIT) == VMID_FIT ? item->wpos.y + ydiff / 2
                                                 : item->wpos.y + ydiff;
        int w = (f & X_FIT) == X_FIT ? item->wpos.cx + xdiff : item->wpos.cx;
        int h = (f & Y_FIT) == Y_FIT ? item->wpos.cy + ydiff : item->wpos.cy;

        if (!(hdwp = ::DeferWindowPos(hdwp, item->hWnd, 0, x, y, w, h, dwFlg)))
            return FALSE;
    }
    ::EndDeferWindowPos(hdwp);

    return TRUE;
}

/*----------------------------------------------------------------
 * TApp
 *---------------------------------------------------------------*/

TApp *TApp::tapp = NULL;

TApp::TApp(HINSTANCE _hI) {
    hI = _hI;
    mainWnd = NULL;
    preWnd = NULL;
    tapp = this;
    twinId = 1;

    ::CoInitialize(NULL);
    ::InitCommonControls();
}

TApp::~TApp() {
    delete mainWnd;
    ::CoUninitialize();
}

int TApp::Run(void) {
    InitApp();
    InitWindow();

    MSG msg;

    while (::GetMessage(&msg, NULL, 0, 0)) {
        if (PreProcMsg(&msg)) {
            continue;
        }

        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

// for TranslateAccel & IsDialogMessage
BOOL TApp::PreProcMsg(MSG *msg) {
    for (HWND hWnd = msg->hwnd; hWnd; hWnd = ::GetParent(hWnd)) {
        TWin *win = SearchWnd(hWnd);

        if (win) {
            return win->PreProcMsg(msg);
        }
    }

    return FALSE;
}

LRESULT CALLBACK TApp::WinProc(HWND hWnd, UINT uMsg, WPARAM wParam,
                               LPARAM lParam) {
    TApp *app = TApp::GetApp();
    TWin *win = app->SearchWnd(hWnd);

    if (win) {
        return win->WinProc(uMsg, wParam, lParam);
    }

    if ((win = app->preWnd)) {
        app->preWnd = NULL;
        app->AddWinByWnd(win, hWnd);
        return win->WinProc(uMsg, wParam, lParam);
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

BOOL TApp::InitApp(void) { return 0; }

void TApp::Idle(DWORD timeout) {
    TApp *app = TApp::GetApp();
    DWORD start = GetTickCount();
    MSG msg;

    while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        if (app->PreProcMsg(&msg)) {
            continue;
        }

        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
        if (GetTickCount() - start >= timeout) {
            break;
        }
    }
}
